/*
 * CircularBuffer.hpp:
 *
 * Buffer used to efficiently store data in ring data structure. Uses an externally supplied
 * data store as the backing for this buffer. Thus it is dependent on receiving sole ownership
 * of the supplied buffer.
 *
 * Note: this given implementation loses one byte of the data store in order to ensure that a
 * separate wrap-around tracking variable is not needed.
 *
 *  Created on: Apr 4, 2019
 *      Author: lestarch
 *  Revised March 2022
 *      Author: bocchino
 */

#ifndef TYPES_CIRCULAR_BUFFER_HPP
#define TYPES_CIRCULAR_BUFFER_HPP

#include <Fw/FPrimeBasicTypes.hpp>
#include <Fw/Types/Serializable.hpp>

namespace Types {

class CircularBuffer {
    friend class CircularBufferTester;

  public:
    /**
     * Circular buffer constructor. Wraps the supplied buffer as the new data store. Buffer
     * size is supplied in the 'size' argument.
     *
     * Note: specification of storage buffer must be done using `setup` before use.
     */
    CircularBuffer();

    /**
     * Circular buffer constructor. Wraps the supplied buffer as the new data store. Buffer
     * size is supplied in the 'size' argument. This is equivalent to calling the no-argument constructor followed
     * by setup(buffer, size).
     *
     * Note: ownership of the supplied buffer is held until the circular buffer is deallocated
     *
     * \param buffer: supplied buffer used as a data store.
     * \param size: the of the supplied data store.
     */
    CircularBuffer(U8* const buffer, const FwSizeType size);

    /**
     * Wraps the supplied buffer as the new data store. Buffer size is supplied in the 'size' argument. Cannot be
     * called after successful setup.
     *
     * Note: ownership of the supplied buffer is held until the circular buffer is deallocated
     *
     * \param buffer: supplied buffer used as a data store.
     * \param size: the of the supplied data store.
     */
    void setup(U8* const buffer, const FwSizeType size);

    /**
     * Serialize a given buffer into this circular buffer. Will not accept more data than
     * space available. This means it will not overwrite existing data.
     * \param buffer: supplied buffer to be serialized.
     * \param size: size of the supplied buffer.
     * \return Fw::FW_SERIALIZE_OK on success or something else on error
     */
    Fw::SerializeStatus serialize(const U8* const buffer, const FwSizeType size);

    /**
     * Deserialize data into the given variable without moving the head index
     * \param value: value to fill
     * \param offset: offset from head to start peak. Default: 0
     * \return Fw::FW_SERIALIZE_OK on success or something else on error
     */
    Fw::SerializeStatus peek(char& value, FwSizeType offset = 0) const;
    /**
     * Deserialize data into the given variable without moving the head index
     * \param value: value to fill
     * \param offset: offset from head to start peak. Default: 0
     * \return Fw::FW_SERIALIZE_OK on success or something else on error
     */
    Fw::SerializeStatus peek(U8& value, FwSizeType offset = 0) const;
    /**
     * Deserialize data into the given variable without moving the head index
     * \param value: value to fill
     * \param offset: offset from head to start peak. Default: 0
     * \return Fw::FW_SERIALIZE_OK on success or something else on error
     */
    Fw::SerializeStatus peek(U32& value, FwSizeType offset = 0) const;

    /**
     * Deserialize data into the given buffer without moving the head variable.
     * \param buffer: buffer to fill with data of the peek
     * \param size: size in bytes to peek at
     * \param offset: offset from head to start peak. Default: 0
     * \return Fw::FW_SERIALIZE_OK on success or something else on error
     */
    Fw::SerializeStatus peek(U8* buffer, FwSizeType size, FwSizeType offset = 0) const;

    /**
     * Rotate the head index, deleting data from the circular buffer and making
     * space. Cannot rotate more than the available space.
     * \param amount: amount to rotate by (in bytes)
     * \return Fw::FW_SERIALIZE_OK on success or something else on error
     */
    Fw::SerializeStatus rotate(FwSizeType amount);

    /**
     * Get the number of bytes allocated in the buffer
     * \return number of bytes
     */
    FwSizeType get_allocated_size() const;

    /**
     * Get the number of free bytes, i.e., the number
     * of bytes that may be stored in the buffer without
     * deleting data and without exceeding the buffer capacity
     */
    FwSizeType get_free_size() const;

    /**
     * Get the logical capacity of the buffer, i.e., the number of available
     * bytes when the buffer is empty
     */
    FwSizeType get_capacity() const;

    /**
     * Return the largest tracked allocated size
     */
    FwSizeType get_high_water_mark() const;

    /**
     * Clear tracking of the largest allocated size
     */
    void clear_high_water_mark();

  private:
    /**
     * Returns a wrap-advanced index into the store.
     * \param idx: index to advance and wrap.
     * \param amount: amount to advance
     * \return: new index value
     */
    FwSizeType advance_idx(FwSizeType idx, FwSizeType amount = 1) const;
    //! Physical store backing this circular buffer
    U8* m_store;
    //! Size of the physical store
    FwSizeType m_store_size;
    //! Index into m_store of byte zero in the logical store.
    //! When memory is deallocated, this index moves forward and wraps around.
    FwSizeType m_head_idx;
    //! Allocated size (size of the logical store)
    FwSizeType m_allocated_size;
    //! Maximum allocated size
    FwSizeType m_high_water_mark;
};
}  // End Namespace Types
#endif
